/*
 * TensorBase.h
 *
 *  Created on: Sep 18, 2011
 *      Author: phopon
 */
#pragma once
#ifndef TENSORBASE_H_
#define TENSORBASE_H_

#include <vector>
#include <algorithm>
#include <initializer_list>
#include <functional>

#include "TensorIterator.h"
#include "Exception.h"
#include "cmpMatCommon.h"

namespace cmpMat
{

#pragma warning(disable:4018) // for all the signed/unsigned code comparisons for template
#pragma warning(disable:4800) // for converting stream to bool
#pragma warning(disable:4482) // for using enum as strong enum
template <typename _elementType>
class TensorBase
{
public:
    typedef std::vector<CmpMatPtr> _dimension_type;
    typedef typename _dimension_type::const_iterator _const_dimension_iterator;
    typedef typename _dimension_type::iterator _dimension_iterator;
    typedef _TensorIterator<_elementType> Iterator;
    typedef _TensorIterator<const _elementType> const_Iterator;
protected:
    // Internal data
    CmpMatPtr _rank;
    _dimension_type _dimensions;

    CmpMatPtr getIndex_Check(const _dimension_type& _ndx)
    {
        if (_ndx.size() != _rank)
            throw cmpMat::exception::Exception("invalid index rank");

        CmpMatPtr multiplier = 1;
        CmpMatPtr index = 0;
        for (_const_dimension_iterator _ndxIter = _ndx.begin(),
                _dimIter = _dimensions.begin();
             _ndxIter != _ndx.end();
             _ndxIter++, _dimIter++
             )
        {
            if (*_ndxIter >= *_dimIter)
                throw cmpMat::exception::Exception("index out of range");
            index += *_ndxIter * multiplier;
            multiplier *= *_dimIter;
        }

        return index;
    }
    CmpMatPtr getIndex_NoCheck(const _dimension_type& _ndx)
    {
        CmpMatPtr multiplier = 1;
        CmpMatPtr index = 0;
        for (_const_dimension_iterator _ndxIter = _ndx.begin(),
                _dimIter = _dimensions.begin();
             _ndxIter != _ndx.end();
             _ndxIter++, _dimIter++
             )
        {
            index += *_ndxIter * multiplier;
            multiplier *= *_dimIter;
        }
        return index;
    }

    // CONSTRUCTORS
    // 1/2/3/4 size constructor
    TensorBase()
    { SetSize(); }
    explicit
    TensorBase(const CmpMatPtr& size1)
    { SetSize(size1); }
    TensorBase(const CmpMatPtr& size1, const CmpMatPtr& size2)
    { SetSize(size1, size2); }
    TensorBase(const CmpMatPtr& size1, const CmpMatPtr& size2, const CmpMatPtr& size3)
    { SetSize(size1,size2,size3); }
    TensorBase(const CmpMatPtr& size1, const CmpMatPtr& size2, const CmpMatPtr& size3, const CmpMatPtr& size4)
    { SetSize(size1,size2,size3,size4); }
    // any size constructor
    explicit
    TensorBase(const _dimension_type& dimensions)
    { SetSize(dimensions); }
    // copy constructors
    TensorBase(const TensorBase& other)
    { *this = other; }
    TensorBase(TensorBase&& other)
    { *this = std::move(other); }
    virtual TensorBase* clone() const = 0;
    //END CONSTRUCTORS

    // set size after construction
    virtual void SetSize()
    {
        _rank = 0;
        _dimensions = _dimension_type(0,0);
    }
    virtual void SetSize(const CmpMatPtr& size1)
    {
        _rank = 1;
        _dimensions = _dimension_type(1,size1);
    }
    virtual void SetSize(const CmpMatPtr& size1, const CmpMatPtr& size2)
    {
        _rank = 2;
        CmpMatPtr vec[] = {size1, size2};
        _dimensions = _dimension_type(vec, vec + 2);
    }
    virtual void SetSize(const CmpMatPtr& size1, const CmpMatPtr& size2, const CmpMatPtr& size3)
    {
        _rank = 3;
        CmpMatPtr vec[] = {size1, size2, size3};
        _dimensions = _dimension_type(vec, vec + 3);
    }
    virtual void SetSize(const CmpMatPtr& size1, const CmpMatPtr& size2, const CmpMatPtr& size3, const CmpMatPtr& size4)
    {
        _rank = 4;
        CmpMatPtr vec[] = {size1, size2, size3, size4};
        _dimensions = _dimension_type(vec, vec + 4);
    }
    virtual void SetSize(const _dimension_type& dimensions)
    {
        _rank = dimensions.size();
        _dimensions = dimensions;
    }
    // end set size

public:
    // Capacity
    CmpMatPtr size() const
    {
        CmpMatPtr _size;
        if (_dimensions.size() == 0)
            _size = 0;
        else
            _size = 1;
        for (_const_dimension_iterator iter = _dimensions.begin();
                iter != _dimensions.end();
                ++iter)
        { _size *= *iter; }
        return _size;
    }
    CmpMatPtr size(const CmpMatPtr& dim) const
    {
        if (dim >= _dimensions.size())
            throw cmpMat::exception::Exception("dimension out of bounds");
        return _dimensions[dim];
    }
    CmpMatPtr size0() const { return size(0); }
    CmpMatPtr size1() const { return size(1); }
    CmpMatPtr size2() const { return size(2); }
    CmpMatPtr size3() const { return size(3); }
    CmpMatPtr size4() const { return size(4); }
    const _dimension_type& Dimensions() const { return _dimensions; }

    CmpMatPtr rank() const { return _rank; }
    bool empty() const
    {
        return size() == 0;
    }
    // End Capacity

        // elementwise operators
    void fill(const _elementType& _val)
    {
        for (Iterator iter = begin();
             iter != end();
             ++iter)
        {
            *iter = _val;
        }
    }
    void setElems(std::function<_elementType(CmpMatPtr, _elementType)> _f)
    {
        CmpMatPtr num = 0;
        for (Iterator iter = begin();
                iter != end();
                ++iter)
        {
            *iter = _f(num++, *iter);
        }
    }
    void setElemsV(std::function<_elementType(CmpMatPtr)> _f)
    {
        CmpMatPtr num = 0;
        for (Iterator iter = begin();
                iter != end();
                ++iter)
        {
            *iter = _f(num++, *iter);
        }
    }
    // end elementwise operators

    // copy operators
    TensorBase& operator=(const TensorBase& other)
    {
        if (&other != this)
        {
            _rank = other._rank;
            _dimensions = other._dimensions;
        }
        return *this;
    }
    TensorBase& operator=(TensorBase&& other)
    {
        if (&other != this)
        {
            _rank = std::move(other._rank);
            _dimensions = std::move(other._dimensions);
        }
        return *this;
    }
    template <typename _T> friend std::auto_ptr<_T> clone(_T const* t);
    // end copy operators
public:
    bool operator!=(const TensorBase& other) const
    { return !(*this == other); }
    bool isSizeEqual(const TensorBase& other) const
    {
        if (rank() != other.rank())
            return false;
        for (int i = 0; i < rank(); ++i)
            if (size(i) != other.size(i))
                return false;
        return true;
    }
    bool operator==(const TensorBase& other) const
    {
        if (&other == this)
            return true;
        if (!isSizeEqual(other))
            return false;
        for (const_Iterator iter1 = cbegin(), iter2 = other.cbegin();
                iter1 != cend();
                ++iter1, ++iter2)
            if (*iter1 != *iter2)
                return false;
        return true;
    }

    // Accessor operators
    _elementType& at(const _dimension_type& _ndx)
    {
        return this->operator [](this->getIndex_Check(_ndx));
    }
    _elementType& at(const CmpMatPtr& dim1)
    {
        return at(std::vector<CmpMatPtr>(1,dim1));
    }
    _elementType& at(const CmpMatPtr& dim1, const CmpMatPtr& dim2)
    {
        CmpMatPtr v[] = {dim1, dim2};
        return at(std::vector<CmpMatPtr>(v,v+2));
    }
    _elementType& at(const CmpMatPtr& dim1, const CmpMatPtr& dim2, const CmpMatPtr& dim3)
    {
        CmpMatPtr v[] = {dim1, dim2, dim3};
        return at(std::vector<CmpMatPtr>(v,v+3));
    }
    _elementType& at(const CmpMatPtr& dim1, const CmpMatPtr& dim2,
            const CmpMatPtr& dim3, const CmpMatPtr& dim4)
    {
        CmpMatPtr v[] = {dim1, dim2, dim3, dim4};
        return at(std::vector<CmpMatPtr>(v,v+4));
    }

    _elementType& operator()(const _dimension_type& _ndx)
    {
        return this->operator [](_ndx);
    }
    _elementType& operator()(const CmpMatPtr& dim1)
    {
        return this->operator [](dim1);
    }
    _elementType& operator()(const CmpMatPtr& dim1, const CmpMatPtr& dim2)
    {
        CmpMatPtr v[] = {dim1, dim2};
        return this->operator [](std::vector<CmpMatPtr>(v,v+2));
    }
    _elementType& operator()(const CmpMatPtr& dim1, const CmpMatPtr& dim2, const CmpMatPtr& dim3)
    {
        CmpMatPtr v[] = {dim1, dim2, dim3};
        return this->operator [](std::vector<CmpMatPtr>(v,v+3));
    }
    _elementType& operator()(const CmpMatPtr& dim1, const CmpMatPtr& dim2,
            const CmpMatPtr& dim3, const CmpMatPtr& dim4)
    {
        CmpMatPtr v[] = {dim1, dim2, dim3, dim4};
        return this->operator [](std::vector<CmpMatPtr>(v,v+4));
    }

    _elementType& operator[](const _dimension_type& _ndx)
    {
        return this->operator [](this->getIndex_NoCheck(_ndx));
    }
    _elementType& front()
    { return (*this)[0]; }
    _elementType& back()
    { return (*this)[std::max<CmpMatPtr>(0, size()-1)]; }

    // const accessors
    const _elementType& at(const _dimension_type& _ndx) const
    { return const_cast<TensorBase*>(this)->at(_ndx); }
    const _elementType& at(const CmpMatPtr& dim1) const
    { return const_cast<TensorBase*>(this)->at(dim1); }
    const _elementType& at(const CmpMatPtr& dim1, const CmpMatPtr& dim2)const
    { return const_cast<TensorBase*>(this)->at(dim1, dim2); }
    const _elementType& at(const CmpMatPtr& dim1, const CmpMatPtr& dim2, const CmpMatPtr& dim3)const
    { return const_cast<TensorBase*>(this)->at(dim1,dim2,dim3); }
    const _elementType& at(const CmpMatPtr& dim1, const CmpMatPtr& dim2,
            const CmpMatPtr& dim3, const CmpMatPtr& dim4)const
    { return const_cast<TensorBase*>(this)->at(dim1,dim2,dim3,dim4); }

    const _elementType& operator()(const _dimension_type& _ndx)const
    { return const_cast<TensorBase*>(this)->operator ()(_ndx); }
    const _elementType& operator()(const CmpMatPtr& dim1) const
    { return const_cast<TensorBase*>(this)->operator ()(dim1); }
    const _elementType& operator()(const CmpMatPtr& dim1, const CmpMatPtr& dim2)const
    { return const_cast<TensorBase*>(this)->operator ()(dim1,dim2); }
    const _elementType& operator()(const CmpMatPtr& dim1, const CmpMatPtr& dim2, const CmpMatPtr& dim3)const
    { return const_cast<TensorBase*>(this)->operator ()(dim1,dim2,dim3); }
    const _elementType& operator()(const CmpMatPtr& dim1, const CmpMatPtr& dim2,
            const CmpMatPtr& dim3, const CmpMatPtr& dim4) const
    { return const_cast<TensorBase*>(this)->operator ()(dim1,dim2,dim3,dim4); }

    const _elementType& operator[](const _dimension_type& _ndx) const
    { return const_cast<TensorBase*>(this)->operator [](_ndx); }
    const _elementType& front() const
    { return const_cast<TensorBase*>(this)->front(); }
    const _elementType& back() const
    { return const_cast<TensorBase*>(this)->back(); }
    // end const accessors
    // END Accessor operators

    // Virtual functions to overload in children
    virtual _elementType& operator[](const CmpMatPtr& dim1) = 0;

    virtual Iterator begin() = 0;
    virtual Iterator end() = 0;
    
    std::string toString() const
    {
        return toStringFormat("");
    }
    virtual std::string toStringFormat(const std::string& str) const
    {
        return "Tensor does not support print";
    }

    const _elementType& operator[](const CmpMatPtr& dim1) const
    { return const_cast<TensorBase*>(this)->operator [](dim1); }
    const_Iterator cbegin() const
    { return const_Iterator(const_cast<TensorBase*>(this)->begin()); }
    const_Iterator cend() const
    { return const_Iterator(const_cast<TensorBase*>(this)->end()); }
    // END Virtual

#pragma warning(default:4018)
#pragma warning(default:4800)
#pragma warning(default:4482)
};

} /* namespace cmpMat */
#endif /* TENSORBASE_H_ */
